package utils

import (
	"bytes"
	"crypto/md5"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"
	"unicode"

	"github.com/eiannone/keyboard"
	"github.com/teris-io/shortid"

	"github.com/go-ini/ini"
)

func LocalIP() string {
	ip := ""
	if addrs, err := net.InterfaceAddrs(); err == nil {
		for _, addr := range addrs {
			if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsMulticast() && !ipnet.IP.IsLinkLocalUnicast() && !ipnet.IP.IsLinkLocalMulticast() && ipnet.IP.To4() != nil {
				ip = ipnet.IP.String()
			}
		}
	}
	return ip
}

func LocalMAC() (string, error) {
	netInterfaces, err := net.Interfaces()
	if err != nil {
		panic(err.Error())
	}
	mac, macerr := "", errors.New("无法获取到正确的MAC地址")
	for i := 0; i < len(netInterfaces); i++ {
		//fmt.Println(netInterfaces[i])
		if (netInterfaces[i].Flags&net.FlagUp) != 0 && (netInterfaces[i].Flags&net.FlagLoopback) == 0 {
			addrs, _ := netInterfaces[i].Addrs()
			for _, addr := range addrs {
				if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsMulticast() && !ipnet.IP.IsLinkLocalUnicast() && !ipnet.IP.IsLinkLocalMulticast() && ipnet.IP.To4() != nil {
					//fmt.Println(ipnet.IP)
					mac = netInterfaces[i].HardwareAddr.String()
					return mac, nil
				}
			}
		}
	}
	return mac, macerr
}

func MD5(str string) string {
	encoder := md5.New()
	encoder.Write([]byte(str))
	return hex.EncodeToString(encoder.Sum(nil))
}

func CWD() string {
	path, err := os.Executable()
	if err != nil {
		return ""
	}
	return filepath.Dir(path)
}

var workInDirLock sync.Mutex

func WorkInDir(f func(), dir string) {
	wd, _ := os.Getwd()
	workInDirLock.Lock()
	defer workInDirLock.Unlock()
	os.Chdir(dir)
	defer os.Chdir(wd)
	f()
}

func EXEName() string {
	path, err := os.Executable()
	if err != nil {
		return ""
	}
	return strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
}

func HomeDir() string {
	u, err := user.Current()
	if err != nil {
		return ""
	}
	return u.HomeDir
}

func LogDir() string {
	dir := filepath.Join(CWD(), "logs")
	EnsureDir(dir)
	return dir
}
func SslDir() string {
	dir := filepath.Join(CWD(), "ssl")
	EnsureDir(dir)
	return dir
}
func ErrorLogFilename() string {
	return filepath.Join(LogDir(), fmt.Sprintf("%s-error.log", strings.ToLower(EXEName())))
}

/*just for arm*/
func ExcelDataDir() string {

	var dir = ""
	if runtime.GOARCH == "arm" {
		dir = "/tmp/"
	} else {
		dir = CWD()
	}

	_dir := Conf().Section("").Key("data_dir").Value()
	if _dir != "" {
		dir = _dir
	}

	//if utils.Debug {
	log.Println("data_dir path -->", dir)
	//}

	dir = ExpandHomeDir(dir)
	EnsureDir(dir)
	return dir
}

func DataDir() string {
	dir := CWD()
	_dir := Conf().Section("").Key("data_dir").Value()
	if _dir != "" {
		dir = _dir
	}
	//if utils.Debug {
	log.Println("data_dir path -->", dir)
	//}
	dir = ExpandHomeDir(dir)
	EnsureDir(dir)
	return dir
}

var FlagVarConfFile string

func ConfFile() string {
	if len(os.Args) > 1 {
		n := len(os.Args)
		for i := 0; i < n; i++ {
			if os.Args[i] == "-c" {
				if ChinesePath(os.Args[i+1]) {
					return filepath.Join(CWD(), strings.ToLower(EXEName())+".ini")
				}
				return os.Args[i+1]
			}
		}
	}
	if FlagVarConfFile != "" {
		return FlagVarConfFile
	}
	if Exist(ConfFileDev()) {
		return ConfFileDev()
	}
	return filepath.Join(CWD(), "conf", strings.ToLower(EXEName())+".ini")
}

func ConfFileDev() string {
	return filepath.Join(CWD(), strings.ToLower(EXEName())+".dev.ini")
}

var FlagVarDBFile string

//中文路径
func ChinesePath(str string) bool {
	for _, r := range str {
		if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) {
			return true
		}
	}
	return false
}

func DBFile() string {
	if len(os.Args) > 1 {
		n := len(os.Args)
		for i := 0; i < n; i++ {
			if os.Args[i] == "-d" {
				if ChinesePath(os.Args[i+1]) {
					return filepath.Join(CWD(), "database", strings.ToLower(EXEName()+".db"))
				}
				return os.Args[i+1]
			}
		}
	}
	if FlagVarDBFile != "" {
		return FlagVarDBFile
	}
	if Exist(DBFileDev()) {
		return DBFileDev()
	}
	return filepath.Join(CWD(), "database", strings.ToLower(EXEName()+".db"))
}

func DBFileDev() string {
	return filepath.Join(CWD(), strings.ToLower(EXEName())+".dev.db")
}

var conf *ini.File

func Conf() *ini.File {
	if conf != nil {
		return conf
	}
	fmt.Println("===ConfFile:", ConfFile())
	if _conf, err := ini.InsensitiveLoad(ConfFile()); err != nil {
		_conf, _ = ini.LoadSources(ini.LoadOptions{Insensitive: true}, []byte(""))
		conf = _conf
	} else {
		conf = _conf
	}
	return conf
}

func ReloadConf() *ini.File {
	if _conf, err := ini.InsensitiveLoad(ConfFile()); err != nil {
		_conf, _ = ini.LoadSources(ini.LoadOptions{Insensitive: true}, []byte(""))
		conf = _conf
	} else {
		conf = _conf
	}
	return conf
}

func SaveToConf(section string, kvmap map[string]string) error {
	var _conf *ini.File
	var err error
	if _conf, err = ini.InsensitiveLoad(ConfFile()); err != nil {
		if _conf, err = ini.LoadSources(ini.LoadOptions{Insensitive: true}, []byte("")); err != nil {
			return err
		}
	}
	sec := _conf.Section(section)
	for k, v := range kvmap {
		sec.Key(k).SetValue(v)
	}
	_conf.SaveTo(ConfFile())
	conf = _conf
	return nil
}

func ExpandHomeDir(path string) string {
	if len(path) == 0 {
		return path
	}
	if path[0] != '~' {
		return path
	}
	if len(path) > 1 && path[1] != '/' && path[1] != '\\' {
		return path
	}
	return filepath.Join(HomeDir(), path[1:])
}

func EnsureDir(dir string) (err error) {
	if _, err = os.Stat(dir); os.IsNotExist(err) {
		err = os.MkdirAll(dir, 0755)
		if err != nil {
			return
		}
	}
	return
}

func Exist(path string) bool {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return false
	}
	return true
}

//GetDirNamesAsc 获取目录所有的文件夹名称,升序
func GetDirNamesAsc(dirpath string) []string {
	return getDirNames(dirpath, true)
}

func getDirNames(dirpath string, isAsc bool) []string {
	var dirList []string
	if Exist(dirpath) {
		list, err := ioutil.ReadDir(dirpath)
		if err != nil {
			return []string{}
		}
		for i := 0; i < len(list); i++ {
			//如果是文件夹或是软连接
			if list[i].IsDir() || IsLink(list[i]) {
				dirList = append(dirList, list[i].Name())
			}
		}
		if isAsc {
			sort.Sort(sort.StringSlice(dirList))
		} else {
			sort.Sort(sort.Reverse(sort.StringSlice(dirList)))
		}
	}
	return dirList
}

//IsLink 判断是否是软连接
func IsLink(f os.FileInfo) bool {
	if strings.HasPrefix(f.Mode().String(), "L") ||
		strings.HasPrefix(f.Mode().String(), "l") {
		return true
	}
	return false
}
func DeepCopy(dst, src interface{}) error {
	var buf bytes.Buffer
	if err := gob.NewEncoder(&buf).Encode(src); err != nil {
		return err
	}
	return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}

//GetFileNamesBySuffix 获取所有文件名称根据后缀
func GetFileNamesBySuffix(dirpath string, suffix string) []string {
	var fileList []string

	if Exist(dirpath) {
		list, err := ioutil.ReadDir(dirpath)
		if err != nil {
			return []string{}
		}
		for i := 0; i < len(list); i++ {
			if !list[i].IsDir() && strings.HasSuffix(list[i].Name(), suffix) {
				fileList = append(fileList, list[i].Name())
			}
		}
		//降序排列
		sort.Sort(sort.Reverse(sort.StringSlice(fileList)))
	}
	return fileList
}

func InArray(need string, needArr []string) bool {
	for _, v := range needArr {
		if need == v {
			return true
		}
	}
	return false
}
func Open(url string) error {
	var cmd string
	var args []string

	switch runtime.GOOS {
	case "windows":
		cmd = "cmd"
		args = []string{"/c", "start"}
	case "darwin":
		cmd = "open"
	default: // "linux", "freebsd", "openbsd", "netbsd"
		cmd = "xdg-open"
	}
	args = append(args, url)
	return exec.Command(cmd, args...).Start()
}

func ShortID() string {
	return shortid.MustGenerate()
}

func PauseExit() {
	log.Println("Press any to exit")
	keyboard.GetSingleKey()
	os.Exit(0)
}

func PauseGo(msg ...interface{}) {
	log.Println(msg...)
	keyboard.GetSingleKey()
}

func IsPortInUse(port int) bool {
	if conn, err := net.DialTimeout("tcp", net.JoinHostPort("", fmt.Sprintf("%d", port)), 3*time.Second); err == nil {
		conn.Close()
		return true
	}
	return false
}

func init() {
	gob.Register(map[string]interface{}{})
	gob.Register(StringArray(""))
	ini.PrettyFormat = false
}

func Indirect(reflectValue reflect.Value) reflect.Value {
	for reflectValue.Kind() == reflect.Ptr {
		reflectValue = reflectValue.Elem()
	}
	return reflectValue
}
